﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Xml.Serialization;
using System.Threading;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Runtime.Serialization;
using System.Windows;

namespace ContestMeter.Common
{
    [DataContract]
    public class Solution : NotifyPropertyChanged//: Task
    {
        public IFileSystem FileSystem { get; set; }
        public const string TaskPropertyName = "Task";
        private Task _task;

        public Solution() : this(new FtpUtilities())
        {
        }

        public Solution(IFileSystem fileSystem)
        {
            ContestMeter.Check.NotNull(fileSystem,"fileSystem");
            FileSystem = fileSystem;
        }

        [DataMember]
        public Task Task
        {
            get { return _task; }
            set
            {
                if (_task != value)
                {
                    _task = value;
                    OnPropertyChanged(TaskPropertyName);
                    OnPropertyChanged("ExecutableName");
                    OnPropertyChanged("Weight");
                    OnPropertyChanged("Name");
                    OnPropertyChanged("CheckerName");
                    OnPropertyChanged("TimeLimit");
                    OnPropertyChanged("TestsFolder");
                }
            }
        }

        public string ExecutableName
        {
            get { return Task.ExecutableName; }
        }

        public int Weight
        {
            get { return Task.Weight; }
        }

        public string Name
        {
            get { return Task.Name; }
        }

        public string CheckerName
        {
            get { return Task.CheckerName; }
        }

        public int TimeLimit
        {
            get { return Task.TimeLimit; }
        }

        public string TestsFolder
        {
            get { return Task.TestsFolder; }
        }

        public string InputFileName
        {
            get { return ExecutableName + ".in"; }
        }

        public string OutputFileName
        {
            get { return ExecutableName + ".out"; }
        }

        //public string InputFileName
        //{
        //    get { return "input.txt"; }
        //}

        //public string OutputFileName
        //{
        //    get { return "output.txt"; }
        //}

        public string AnswerFileName
        {
            get { return ExecutableName + ".a"; }
        }


        const string PathPropertyName = "Path";
        private string _path;
        public string Path
        {
            get { return _path; }
            set
            {
                if (_path != value)
                {
                    _path = value;
                    OnPropertyChanged(PathPropertyName);
                }
            }
        }

        public const string DevToolPropertyName = "DevTool";
        private DeveloperTool _devTool;
        [DataMember]
        public DeveloperTool DevTool
        {
            get { return _devTool; }
            set
            {
                if (_devTool != value)
                {
                    _devTool = value;
                    OnPropertyChanged(DevToolPropertyName);
                }
            }
        }


        const string ScorePropertyName = "Score";
        private int _score;
        public int Score
        {
            get { return _score; }
            set
            {
                if (_score != value)
                {
                    _score = value;
                    OnPropertyChanged(ScorePropertyName);
                    OnPropertyChanged("WeightedScore");
                }
            }
        }

        public int WeightedScore
        {
            get { return Score * Weight; }
        }

        const string TestsCountPropertyName = "TestsCount";
        private int _testsCount;
        public int TestsCount
        {
            get { return _testsCount; }
            set
            {
                if (_testsCount != value)
                {
                    _testsCount = value;
                    OnPropertyChanged(TestsCountPropertyName);
                }
            }
        }

        const string FailedChecksPropertyName = "FailedChecks";
        private int _failedChecks;
        public int FailedChecks
        {
            get { return _failedChecks; }
            set
            {
                if (_failedChecks != value)
                {
                    _failedChecks = value;
                    OnPropertyChanged(FailedChecksPropertyName);
                }
            }
        }

        const string FailedRunsPropertyName = "FailedRuns";
        private int _failedRuns;
        public int FailedRuns
        {
            get { return _failedRuns; }
            set
            {
                if (_failedRuns != value)
                {
                    _failedRuns = value;
                    OnPropertyChanged(FailedRunsPropertyName);
                }
            }
        }

        /// <summary>
        /// Имя файла с исходным кодом решения
        /// </summary>
        public string SourceFileName
        {
            get { return System.IO.Path.GetFileName(LocalSourcePath); }
        }

        public IConfiguration Configuration { get; set; }
        public Participant Participant { get; set; }

        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        public void Check(string workingDirectory, string solutionsFolder)
        {
            Score = 0;
            FailedRuns = 0;
            FailedChecks = 0;
            DoCheck(workingDirectory, solutionsFolder);
            OnPropertyChanged(ScorePropertyName);
            OnPropertyChanged(FailedChecksPropertyName);
            OnPropertyChanged(FailedRunsPropertyName);
        }
        private void DoCheck(string workingDirectory, string solutionsFolder)
        {
            var testsFolder = FileSystem.Combine(Configuration.TestsFolder, ExecutableName);

            Compile(workingDirectory, solutionsFolder);
            var tests = FileSystem.List(testsFolder);
            if (tests != null)
            {
                tests = tests.Where(x => !x.Contains(".a"));

                TestsCount = tests.Count();
                foreach (var infile in tests)
                {
                    var infileUrl = FileSystem.Combine(testsFolder, infile);

                    Run(infileUrl, infileUrl + ".a", GetCheckerUrl());
                }
            }
        }

        private string GetCheckerUrl()
        {
            return FileSystem.Combine(FileSystem.Combine(Configuration.Site, "Checkers"), CheckerName);
        }

        private void Run(string input, string answer, string checker)
        {
            if (!File.Exists(Path))
            {
                FailedRuns++;
                return;
            }
            var workingDirectory = System.IO.Path.Combine(System.IO.Path.GetTempPath(),
                Guid.NewGuid().ToString("N"));

            Directory.CreateDirectory(workingDirectory);

            var tempInput = System.IO.Path.Combine(workingDirectory, InputFileName);
            var tempOutput = System.IO.Path.Combine(workingDirectory, OutputFileName);
            var tempAnswer = System.IO.Path.Combine(workingDirectory, AnswerFileName);
            var tempExe = System.IO.Path.Combine(workingDirectory, ExecutableName + ".exe");
            var tempChecker = System.IO.Path.Combine(workingDirectory, CheckerName);

            FileSystem.Download(input, tempInput);
            FileSystem.Download(answer, tempAnswer);
            FileSystem.Download(checker, tempChecker);

            File.Copy(Path, tempExe);

            SafeDelete(tempOutput);

            if (Run(workingDirectory, TimeLimit))
            {
                FileSystem.Download(answer, tempAnswer);

                if (Check(workingDirectory, tempInput, tempOutput, tempAnswer, tempChecker))
                {
                    Score++;
                }
                else
                {
                    FailedChecks++;
                }
            }
            else
            {
                FailedRuns++;
            }

            Clean(workingDirectory);
        }

        private bool Check(string workingDirectory, string tempInput, string tempOutput, string tempAnswer, string tempChecker)
        {
            var result = System.IO.Path.Combine(workingDirectory, "result.xml");

            var procInfo = new ProcessStartInfo
            {
                FileName = tempChecker,
                Arguments = string.Format("\"{3}\" \"{0}\" \"{1}\" \"{2}\" -appes",
                    tempOutput, tempAnswer, result, tempInput),
                CreateNoWindow = true,
                UseShellExecute = false,
                WorkingDirectory = workingDirectory,
                RedirectStandardOutput = true,
            };

            Process process = new Process();

            try
            {
                process.StartInfo = procInfo;
                process.Start();
                process.WaitForExit();

                XmlSerializer serializer = new XmlSerializer(typeof(CheckerResult));

                using (var stream = File.OpenRead(result))
                {
                    var checkResult = (CheckerResult)serializer.Deserialize(stream);

                    return checkResult.IsAccepted;
                }
            }
            finally
            {
                if (File.Exists(result))
                {
                    File.Delete(result);
                }
            }
        }


        private bool Run(string workingDirectory, int timeLimit)
        {
            var startInfo = new ProcessStartInfo
                {
                    FileName = System.IO.Path.Combine(workingDirectory, ExecutableName + ".exe"),
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    WindowStyle = ProcessWindowStyle.Hidden,
                    WorkingDirectory = workingDirectory,
                    ErrorDialog = false,
                    LoadUserProfile = false,
                };

            var process = new Process();
            process.StartInfo = startInfo;

            if (process.Start())
            {
                if (process.WaitForExit(timeLimit * 1000))
                {
                    return true;
                }
                else
                {
                    process.Kill();
                    if (!process.HasExited)
                    {
                        process.WaitForExit();
                    }
                }
            }

            return false;

        }

        #region File system helpers
        private static void SafeDelete(string fileName)
        {
            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }
        }

        private static void Clean(string folder)
        {
            if (Directory.Exists(folder))
            {
                try
                {
                    Directory.Delete(folder, true);
                }
                catch { }
            }
        }

        #endregion

        public void Upload()
        {
            if (File.Exists(LocalSourcePath))
            {
                var folder = string.Format("{0}/{1}", Configuration.SolutionsFolder, ExecutableName);
                if (!FileSystem.Exists(FileSystem.Combine(Configuration.SolutionsFolder, ExecutableName)))
                {
                    FileSystem.CreateFolder(folder);
                }
                FileSystem.Upload(LocalSourcePath, FileSystem.Combine(folder, SourceFileName));
            }
        }

        public void Compile(string workingDirectory, string solutionsFolder)
        {
            if (!DevTool.IsExeFile)
            {
                var exeFile = System.IO.Path.Combine(workingDirectory, ExecutableName + ".exe");
                SafeDelete(exeFile);
                var sourcePath = System.IO.Path.Combine(workingDirectory, SourceFileName);
                var taskUrl = string.Format("{0}/{1}", solutionsFolder, ExecutableName);
                if (FileSystem.Exists(FileSystem.Combine(taskUrl, SourceFileName)))
                {
                    var url = string.Format("{0}/{1}", taskUrl, SourceFileName);
                    FileSystem.Download(url, sourcePath);
                    DevTool.Compile(workingDirectory, sourcePath);
                    Path = exeFile;
                }
            }
        }

        /// <summary>
        /// Путь к файлу с исходным кодом решения на локальном компьютере. 
        /// Этот путь участник указывает при сдаче задания и отправке на проверку.
        /// </summary>
        [DataMember]
        public string LocalSourcePath { get; set; }
    }
}
